function VisualizerElements(eden, s0, s1, old,
    edenSizeEle, s0SizeEle, s1SizeEle, oldSizeEle,
    edenHintEle, s0HintEle, s1HintEle, oldHintEle,
    currentEventEle, currentEventInfoEle) {
    this.eden = eden;
    this.s0 = s0;
    this.s1 = s1;
    this.old = old;
    this.edenSizeEle = edenSizeEle;
    this.s0SizeEle = s0SizeEle;
    this.s1SizeEle = s1SizeEle;
    this.oldSizeEle = oldSizeEle;
    this.edenHintEle = edenHintEle;
    this.s0HintEle = s0HintEle;
    this.s1HintEle = s1HintEle;
    this.oldHintEle = oldHintEle;
    this.currentEventEle = currentEventEle;
    this.currentEventInfoEle = currentEventInfoEle;
}

class Visualizer {
    constructor(visualizerElements, events) {
        this.visualizerElements = visualizerElements;
        this.events = events;
        this.startIndex = 0;
        this.unreachables = [];
    }
}

class YoungVisualizer {
    constructor(visualizerElements, events) {
        this.elements = visualizerElements;
        this.events = events;
        this.survivorContainer = visualizerElements.s0;
        this.companionSurvivorContainer = visualizerElements.s1;
        this.startIndex = 0;
        this.unreachables = [];
    }

    createObjElemContent(klass, size, name) {
        return `<label>${klass}</label>
        <label>${name}</label>
        <label>${size}</label>`;
    }

    createObjElem(container, tag, klass, size, name) {
        let content = this.createObjElemContent(klass, size, name);
        let div = document.createElement("div");
        div.id = `${container.id}-${tag}`;
        div.innerHTML = content;
        return div;
    }

    deleteUnreachableElem() {
        for (const objElem of this.unreachables) {
            objElem.innerHTML = "";
            objElem.style.display = "none";
        }
    }

    deleteUnreachableElem(container) {
        for (const objElem of this.unreachables) {
            if (objElem.parentElement != container) continue;
            container.removeChild(objElem);
        }
    }

    clearEdenAndCompanionElems() {
        this.elements.eden.innerHTML = "";
        this.companionSurvivorContainer.innerHTML = "";
    }

    tagUnreachable(container, tag) {
        let id = `${container.id}-${tag}`;
        let objElem = document.getElementById(id);
        if (objElem == null) return;
        this.setUnreachableTag(objElem);
        this.addUnreachables(objElem);
    }

    setUnreachableTag(objElem) {
        objElem.style.background = "#ff7675";
        for (const label of objElem.children) {
            label.style.color = "white";
        }
    }

    addUnreachables(objElem) {
        this.unreachables.push(objElem);
    }

    addDisappearAnim(objElem) {
        objElem.style.animationName = "disappear-scalex";
        objElem.style.animationDuration = "0.5s";
        objElem.style.animationFillMode = "forwards";
    }

    addAppearAnim(objElem) {
        objElem.style.animationName = "appear-scalex";
        objElem.style.animationDuration = "0.5s";
        objElem.style.animationFillMode = "forwards";
    }

    moveObjElem(fromContainer, toContainer, tags) {
        for (const tag of tags) {
            let fromId = `${fromContainer.id}-${tag}`;
            let fromObjElem = document.getElementById(fromId);
            if (fromObjElem == null) return;
            // Push into unreachables
            this.unreachables.push(fromObjElem);
            // Get info
            let klass = fromObjElem.children[0].innerHTML;
            let name = fromObjElem.children[1].innerHTML;
            let size = fromObjElem.children[2].innerHTML;
            this.addDisappearAnim(fromObjElem);
            // Create toElems in toContainer
            let toObjElem = this.createObjElem(toContainer, tag, klass, size, name);
            this.addAppearAnim(toObjElem);
            toContainer.appendChild(toObjElem);
        }
    }

    copyObjElem(fromContainer, toContainer, tags) {
        for (const tag of tags) {
            let fromId = `${fromContainer.id}-${tag}`;
            let fromObjElem = document.getElementById(fromId);
            if (fromObjElem == null) return;
            // Get info
            let klass = fromObjElem.children[0].innerHTML;
            let name = fromObjElem.children[1].innerHTML;
            let size = fromObjElem.children[2].innerHTML;
            // Create toElems in toContainer
            let toObjElem = this.createObjElem(toContainer, tag, klass, size, name);
            this.addAppearAnim(toObjElem);
            toContainer.appendChild(toObjElem);
        }
    }

    reset() {
        this.startIndex = 0;
        this.unreachables = [];
        this.survivorContainer = visualizerElements.s0;
        this.companionSurvivorContainer = visualizerElements.s1;
        for (const element of Object.values(this.elements)) {
            if (element == null || element == undefined) continue;
            element.innerHTML = "";
        }
    }

    next() {
        if (this.startIndex >= this.events.length) return;
        let event = this.events[this.startIndex];
        console.log(event.type);

        let eventEle = this.elements.currentEventEle;
        let eventInfoEle = this.elements.currentEventInfoEle;

        if (event.type == "VMInit") {
            eventEle.innerHTML = "VM Init";
            eventInfoEle.innerHTML = "Java VM finished init";
        } else if (event.type == "ObjectAlloc") {
            this.elements.currentEventEle.innerHTML = "Object Allocation";
            this.elements.currentEventInfoEle.innerHTML = "Finish to create object";
            let position;
            if (event.objPosition == "Eden") {
                position = this.elements.eden;
            } else if (event.objPosition == "S0") {
                position = this.elements.s0;
            } else if (event.objPosition == "S1") {
                position = this.elements.s1;
            } else if (event.objPosition == "Old") {
                position = this.elements.old;
            }
            let objElem = this.createObjElem(position, event.tag, event.objSignature, event.objSize, "Unknown");
            this.addAppearAnim(objElem);
            this.elements.eden.appendChild(objElem);
        } else if (event.type == "MinorGCStart") {
            eventEle.innerHTML = "Minor GC Start";
            eventInfoEle.innerHTML = "MinorGCStart";
        } else if (event.type == "Mark") {
            // Not only MinorGC has Mark!
            eventEle.innerHTML = "GC Mark";
            eventInfoEle.innerHTML = "";
            if (event.objPosition == "Eden") {
                this.tagUnreachable(this.elements.eden, event.tag);
                this.tagUnreachable(this.companionSurvivorContainer, event.tag);
            } else if (event.objPosition == "Old") {
                this.tagUnreachable(this.elements.old, event.tag);
            }
        } else if (event.type == "MinorCopy") {
            eventEle.innerHTML = "Minor GC Copy";
            eventInfoEle.innerHTML = "MinorGCStart";
            if (event.copyTo == "S0") {
                this.survivorContainer = this.elements.s0;
                this.companionSurvivorContainer = this.elements.s1;
            } else {
                this.survivorContainer = this.elements.s1;
                this.companionSurvivorContainer = this.elements.s0;
            }
            this.copyObjElem(this.elements.eden, this.survivorContainer, event.toSObjTags);
            this.copyObjElem(this.companionSurvivorContainer, this.survivorContainer, event.toSObjTags);
            this.copyObjElem(this.elements.eden, this.elements.old, event.toOldObjTags);
            this.copyObjElem(this.companionSurvivorContainer, this.elements.old, event.toOldObjTags);
        } else if (event.type == "MinorDelete") {
            eventEle.innerHTML = "Minor GC Delete";
            eventInfoEle.innerHTML = "MinorGCStart";
            // Clear Eden and S
            for (const objElem of this.elements.eden.children) {
                this.addDisappearAnim(objElem);
            }
            for (const objElem of this.companionSurvivorContainer.children) {
                this.addDisappearAnim(objElem);
            }
            // 大小控制
        } else if (event.type == "MinorGCFinish") {
            eventEle.innerHTML = "Minor GC Finish";
            eventInfoEle.innerHTML = "MinorGCStart";
            this.clearEdenAndCompanionElems();
            // this.deleteUnreachableElem();
            this.unreachables = [];
        } else if (event.type == "FullGCStart") {
            eventEle.innerHTML = "Full GC Start";
            eventInfoEle.innerHTML = "MinorGCStart";
        } else if (event.type == "FullDelete") {
            eventEle.innerHTML = "Full GC Delete";
            eventInfoEle.innerHTML = "MinorGCStart";
            for (const objElem of this.unreachables) {
                this.addDisappearAnim(objElem);
            }
        } else if (event.type == "FullCompat") {
            // 删除元素内部内容
            eventEle.innerHTML = "Full GC Compact";
            eventInfoEle.innerHTML = "MinorGCStart";
            console.log(this.unreachables);
            // 删除Old中不可达对象
            this.deleteUnreachableElem(this.elements.old);
            this.unreachables = [];
        } else if (event.type == "FullMove") {
            eventEle.innerHTML = "Full GC Move";
            eventInfoEle.innerHTML = "MinorGCStart";
            this.moveObjElem(this.elements.eden, this.elements.old, event.toOldObjTags);
            this.moveObjElem(this.companionSurvivorContainer, this.elements.old, event.toOldObjTags);
            this.moveObjElem(this.survivorContainer, this.elements.old, event.toOldObjTags);
        } else if (event.type == "FullGCFinish") {
            eventEle.innerHTML = "Full GC Finish";
            eventInfoEle.innerHTML = "MinorGCStart";
            // 删除搬运过来的不可达对象
            console.log(this.unreachables);
            this.deleteUnreachableElem(this.elements.eden);
            this.deleteUnreachableElem(this.companionSurvivorContainer);
            this.deleteUnreachableElem(this.survivorContainer);
            this.unreachables = [];
        }
        this.startIndex++;
    }
}

class YoungVisualizer2 extends YoungVisualizer {
    constructor(visualizerElements, events) {
        super(visualizerElements, events);
        this.survivorSizeElem = this.elements.s0SizeEle;
        this.survivorHintElem = this.elements.s0HintEle;
        this.companionSurvivorSizeElem = this.elements.s1SizeEle;
        this.companionSurvivorHintElem = this.elements.s1HintEle;
        this.edenCapacity = 0;
        this.survivorCapacity = 0;
        this.oldCapacity = 0;
    }

    setSize(sizeElem, size, capacity) {
        sizeElem.innerHTML = `Size: ${size}/${capacity} Bytes`;
    }

    setJvmSize(hintElem, size) {
        hintElem.innerHTML = `JVM created objects: ${size} Bytes`;
    }

    setHint(hintElem, hint) {
        hintElem.innerHTML = `${hint}`;
    }

    // U can call this in GCFinish Event
    // Just swap content
    swapS0S1SizeHintContent() {
        let hint = "Together with Eden as allocation space";
        let survivorSize = this.survivorSizeElem.innerHTML;
        survivorSize = survivorSize.substring(survivorSize.indexOf(":") + 1, survivorSize.indexOf("/"));
        this.survivorHintElem.innerHTML = hint;

        this.setSize(this.companionSurvivorSizeElem, survivorSize, this.survivorCapacity);
        this.setJvmSize(this.companionSurvivorHintElem, 0);
    }

    reset() {
        super.reset();
        this.survivorSizeElem = this.elements.s0SizeEle;
        this.survivorHintElem = this.elements.s0HintEle;
        this.companionSurvivorSizeElem = this.elements.s1SizeEle;
        this.companionSurvivorHintElem = this.elements.s1HintEle;
    }

    next() {
        if (this.startIndex >= this.events.length) return;
        let event = this.events[this.startIndex];
        console.log(event.type);
        console.log(this.survivorContainer, this.companionSurvivorContainer);

        let eventEle = this.elements.currentEventEle;
        let eventInfoEle = this.elements.currentEventInfoEle;

        if (event.type == "VMInit") {
            eventEle.innerHTML = "VM Init";
            eventInfoEle.innerHTML = "Java VM finished init";
            // Initialize capacity;
            this.edenCapacity = event.edenCapacity;
            this.survivorCapacity = event.survivorCapacity;
            this.oldCapacity = event.oldCapacity;
            // Set size
            this.setSize(this.elements.edenSizeEle, event.jvmObjEdenS0Size, this.edenCapacity + this.survivorCapacity);
            this.setSize(this.survivorSizeElem, 0, this.survivorCapacity);
            this.setSize(this.companionSurvivorSizeElem, 0, this.survivorCapacity);
            this.setSize(this.elements.oldSizeEle, event.jvmObjOldSize, this.oldCapacity);
            this.setJvmSize(this.elements.edenHintEle, event.jvmObjEdenS0Size);
            this.setJvmSize(this.survivorHintElem, 0);
            this.setHint(this.companionSurvivorHintElem, "Together with Eden as allocation space");
            this.setJvmSize(this.elements.oldHintEle, event.jvmObjOldSize);
        } else if (event.type == "ObjectAlloc") {
            this.elements.currentEventEle.innerHTML = "Object Allocation";
            this.elements.currentEventInfoEle.innerHTML = "Finish to create object";
            let position;
            if (event.objPosition == "Eden") {
                position = this.elements.eden;
            } else if (event.objPosition == "S0") {
                position = this.elements.s0;
            } else if (event.objPosition == "S1") {
                position = this.elements.s1;
            } else if (event.objPosition == "Old") {
                position = this.elements.old;
            }
            let objElem = this.createObjElem(position, event.objTag, event.objSignature, event.objSize, "Unknown");
            this.addAppearAnim(objElem);
            this.elements.eden.appendChild(objElem);
            // Set size and hint
            this.setSize(this.elements.edenSizeEle, event.userEdenS0Size + event.jvmObjEdenS0Size, this.edenCapacity + this.survivorCapacity);
            this.setSize(this.elements.oldSizeEle, event.userOldSize + event.jvmObjOldSize, this.oldCapacity);
        } else if (event.type == "MinorGCStart") {
            eventEle.innerHTML = "Minor GC Start";
            eventInfoEle.innerHTML = "MinorGCStart";
        } else if (event.type == "Mark") {
            // Not only MinorGC has Mark!
            eventEle.innerHTML = "GC Mark";
            eventInfoEle.innerHTML = "";
            if (event.objPosition == "Eden") {
                this.tagUnreachable(this.elements.eden, event.objTag);
                this.tagUnreachable(this.companionSurvivorContainer, event.objTag);
            } else if (event.objPosition == "Old") {
                this.tagUnreachable(this.elements.old, event.objTag);
            }
        } else if (event.type == "MinorCopy") {
            eventEle.innerHTML = "Minor GC Copy";
            eventInfoEle.innerHTML = "MinorGCStart";
            if (event.copyTo == "Survivor0") {
                this.survivorContainer = this.elements.s0;
                this.companionSurvivorContainer = this.elements.s1;
                this.survivorSizeElem = this.elements.s0SizeEle;
                this.companionSurvivorSizeElem = this.elements.s1HintEle;
                this.survivorHintElem = this.elements.s0HintEle;
                this.companionSurvivorHintElem = this.elements.s1HintEle;
            } else {
                this.survivorContainer = this.elements.s1;
                this.companionSurvivorContainer = this.elements.s0;
                this.survivorSizeElem = this.elements.s1SizeEle;
                this.companionSurvivorSizeElem = this.elements.s0HintEle;
                this.survivorHintElem = this.elements.s1HintEle;
                this.companionSurvivorHintElem = this.elements.s0HintEle;
            }
            this.copyObjElem(this.elements.eden, this.survivorContainer, event.toSObjTags);
            this.copyObjElem(this.companionSurvivorContainer, this.survivorContainer, event.toSObjTags);
            this.copyObjElem(this.elements.eden, this.elements.old, event.toOldObjTags);
            this.copyObjElem(this.companionSurvivorContainer, this.elements.old, event.toOldObjTags);
            // Set size and hint
            // I have no idea about why the analysis from back end doesn't provide jvmObj info
            // Maybe he only can get info in MinorDelete event
            // Thus this sentence is not necessary
            // If he provides jvmS1Size, U should correct the sentence
            // Copy to old???
            this.setSize(this.survivorSizeElem, event.userS1Size + event.jvmS1Size, this.survivorCapacity);
        } else if (event.type == "MinorDelete") {
            eventEle.innerHTML = "Minor GC Delete";
            eventInfoEle.innerHTML = "MinorGCStart";
            // Clear Eden and S
            for (const objElem of this.elements.eden.children) {
                this.addDisappearAnim(objElem);
            }
            for (const objElem of this.companionSurvivorContainer.children) {
                this.addDisappearAnim(objElem);
            }
            // Set size and hint
            this.setSize(this.elements.edenSizeEle, 0, this.edenCapacity + this.edenCapacity);
            this.setSize(this.survivorSizeElem, event.userEdenS0Size + event.jvmObjEdenS0Size, this.survivorCapacity);
            this.setSize(this.elements.oldSizeEle, event.jvmObjOldSize + event.userOldSize, this.oldCapacity); // I don't know if this is necessary
            this.setJvmSize(this.elements.edenHintEle, 0);
            this.setJvmSize(this.survivorHintElem, event.userEdenS0Size);
            this.setJvmSize(this.elements.oldHintEle, event.jvmObjOldSize);
        } else if (event.type == "MinorGCFinish") {
            eventEle.innerHTML = "Minor GC Finish";
            eventInfoEle.innerHTML = "MinorGCStart";
            this.clearEdenAndCompanionElems();
            this.unreachables = [];
            this.swapS0S1SizeHintContent(); // TEST!!!
        } else if (event.type == "FullGCStart") {
            eventEle.innerHTML = "Full GC Start";
            eventInfoEle.innerHTML = "MinorGCStart";
        } else if (event.type == "FullDelete") {
            eventEle.innerHTML = "Full GC Delete";
            eventInfoEle.innerHTML = "MinorGCStart";
            for (const objElem of this.unreachables) {
                this.addDisappearAnim(objElem);
            }
            // Set size and hint
            this.setSize(this.elements.oldSizeEle, event.userOldSize + event.jvmObjOldSize, this.oldCapacity);
            this.setJvmSize(this.elements.oldHintEle, event.jvmObjOldSize);
            // Maybe Full GC can make impact to Young?
        } else if (event.type == "FullCompact") {
            // 删除元素内部内容
            eventEle.innerHTML = "Full GC Compact";
            eventInfoEle.innerHTML = "MinorGCStart";
            console.log(this.unreachables);
            // 删除Old中不可达对象
            this.deleteUnreachableElem(this.elements.old);
            this.unreachables = [];
        } else if (event.type == "FullMove") {
            eventEle.innerHTML = "Full GC Move";
            eventInfoEle.innerHTML = "MinorGCStart";
            this.moveObjElem(this.elements.eden, this.elements.old, event.toOldObjTags);
            this.moveObjElem(this.companionSurvivorContainer, this.elements.old, event.toOldObjTags);
            this.moveObjElem(this.survivorContainer, this.elements.old, event.toOldObjTags);
            // Set size and hint
            this.setSize(this.elements.edenSizeEle, event.userEdenS0Size + event.jvmObjEdenS0Size, this.edenCapacity + this.survivorCapacity);
            this.setSize(this.elements.oldSizeEle, event.userOldSize + event.jvmObjOldSize, this.oldCapacity);
            this.setJvmSize(this.elements.edenHintEle, event.jvmObjEdenS0Size);
            this.setJvmSize(this.elements.oldHintEle, event.jvmObjOldSize);
        } else if (event.type == "FullGCFinish") {
            eventEle.innerHTML = "Full GC Finish";
            eventInfoEle.innerHTML = "MinorGCStart";
            // 删除搬运过来的不可达对象
            console.log(this.unreachables);
            this.deleteUnreachableElem(this.elements.eden);
            this.deleteUnreachableElem(this.companionSurvivorContainer);
            this.deleteUnreachableElem(this.survivorContainer);
            this.unreachables = [];
        }
        this.startIndex++;
    }
}